Day 26 結尾講到沒人在旁邊看的時候,邊界要事先設好。邊界管的是「能碰到什麼」,還有另一半沒講:任務什麼時候跑、錯過了怎麼辦、跑完怎麼知道結果。
Claude Code 現在有好幾種讓它定期做事的方式,行為差很多。有的 session 一關就停,有的電腦一睡就跳過,有的在雲端但每次都是全新的 clone。選錯的後果通常不是報錯,而是任務靜悄悄地沒跑,或跑了沒做成卻沒人知道。今天攤開五種機制,最後給選擇表。
/loop |
Desktop 排程 | 雲端 Routines | GitHub Actions | 系統 cron + claude -p |
|
|---|---|---|---|---|---|
| 跑在哪 | 本機,現有 session 內 | 本機,桌面 app | Anthropic 雲端 | GitHub runner | 本機,自己排 |
| session 關掉 | 停止(--resume 可還原,背景化可延續) |
照跑,每次開新 session | 照跑 | 照跑 | 看自己的設定 |
| 電腦睡著 | 不觸發 | 該次跳過,醒來補一次 | 不受影響 | 不受影響 | 看排程器 |
| 最短間隔 | 1 分鐘 | 1 分鐘(選單最細是每小時) | 1 小時 | 5 分鐘 | 自己決定 |
| 本機檔案 | 有 | 有 | 沒有,每次 fresh clone | 沒有 | 有 |
| 權限提示 | 沿用 session | 每個任務自己設 | 無,全自動 | 靠 --allowedTools |
自己傳旗標 |

官方的建議:要穩定不靠本機用雲端,要碰本機檔案用 Desktop,session 內輪詢用 /loop。
/loop:session 內的輪詢/loop 有三種用法。給間隔,例如 /loop 5m check the deploy,Claude 轉成 cron 運算式。不給間隔,Claude 每輪自己決定下次多久後再來,在 1 分鐘到 1 小時之間。什麼都不給,跑內建維護提示或專案裡的 .claude/loop.md。
幾個行為值得先知道:
3 9 * * * 這類非整點的時間。--resume 時還原。 要自己重新下 /loop。self-paced 的 loop 如果某一輪既沒有重新排下一次、也沒有停止,Claude Code 會在約 20 分鐘後補一次保底喚醒,那輪再沒有排程就結束 loop。
/loop 常拿來等部署、盯 CI、看長 build。但要等的是事件時,輪詢通常不是最好的方式。可以改用 Channels(讓 CI 的失敗直接推進 session)、/goal(持續做到條件達成,不綁時間間隔),或 Monitor(讓背景指令的輸出直接推進對話)。只有「每隔一段時間看一次」才是 /loop 的位置。
關掉終端機 /loop 就停了。要讓它繼續,在 session 裡用 /bg 把現有 session 移進背景,/loop 的任務會跟著過去(claude --bg 則是直接在背景開新 session)。背景 session 在電腦睡眠時會保留,醒來接續,但關機還是會停。
Desktop 的排程任務存成 ~/.claude/scheduled-tasks/<名稱>/SKILL.md,每次觸發開一個全新 session,出現在側邊欄的 Scheduled 區並跳通知。
幾個要點:
補跑有個陷阱:早上九點的任務,可能在晚上十一點才補跑。任務不知道自己是準時還是補跑的,除非提示教它看時間,例如「只看今天的 commit。如果已經過了下午五點,就不要做 review,改貼一份漏掉的摘要」。
權限是 Desktop 排程最容易卡的地方。每個任務有自己的 permission mode,~/.claude/settings.json 的 allow 規則也適用。任務在 Manual mode 下碰到沒有授權的工具,那次執行會停在原地等你,session 留在側邊欄。處方是建好任務後先按 Run now,守在旁邊把每個權限提示選成「always allow」,之後的執行就會自動放行。例外是標了 requiresUserInteraction 的 MCP 工具,每次都會問,用到它的任務每次都會卡。
Routines 是把提示、repo、一組 connector 存成設定,在 Anthropic 的雲端執行。目前是 research preview,行為與限制可能改。觸發方式有排程、API 呼叫(HTTP POST 加 bearer token)、GitHub 事件(PR、Release)三種,可以組合。
它跟前面兩種最不一樣的地方:
claude mcp add 的 MCP 也不會帶過去,要改用 claude.ai connector 或提交 .mcp.json。/schedule 不會出現。雲端環境預設的網路是受限的,只允許預設白名單,其他主機會回 403,原因是 host_not_allowed。這跟 Day 26 的網路邊界是同一個概念,只是預設值由平台設,connector 的流量不走這條路。
工作本來就在 GitHub 上,用 anthropics/claude-code-action@v1 搭配 schedule 觸發最直接。限制來自 GitHub:排程 workflow 只從預設分支執行,公開 repo 六十天沒有活動會被停用排程。
Claude 端要注意工具授權。純文字提示下,Claude 沒有 shell 也沒有 GitHub API,要用 claude_args 的 --allowedTools 或 settings 的 permissions.allow 給它。排程觸發被 GitHub 歸屬到「最後改過 cron 的人」,如果那是 bot,要列進 allowed_bots,否則 run 會失敗。成本上,設 --max-turns、workflow 逾時與 concurrency 限制。
claude -p:自己扛所有事這是最彈性的做法,環境、密鑰、重試、通知都在自己手上,其他機制替你處理的事這裡都要自己寫。
下面是中性的示意腳本,把環境、鎖、逾時、退出碼、輸出檢查五件事放在同一支。
#!/usr/bin/env bash
set -u
# cron / launchd 的 PATH 極簡,明確設定,不要靠 shell 設定檔
export PATH="/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:$HOME/.local/bin:$PATH"
# 認證由外部提供(權限 600 的 env 檔,寫 ANTHROPIC_API_KEY=...),不寫進腳本
[ -f "${NIGHTLY_ENV_FILE:-}" ] && { set -a; . "$NIGHTLY_ENV_FILE"; set +a; }
STATE_DIR="${NIGHTLY_STATE_DIR:-$HOME/.local/state/nightly-check}"
TIMEOUT_SEC="${NIGHTLY_TIMEOUT_SEC:-600}"
mkdir -p "$STATE_DIR"
LOG="$STATE_DIR/run.log"; LOCK="$STATE_DIR/lock.d"; OUT="$STATE_DIR/last.json"
log() { printf '%s %s\n' "$(date '+%Y-%m-%dT%H:%M:%S%z')" "$*" >> "$LOG"; }
# 鎖:mkdir 是原子操作;鎖裡存 PID,行程已死就視為殘留
if ! mkdir "$LOCK" 2>/dev/null; then
old=$(cat "$LOCK/pid" 2>/dev/null || true)
if [ -n "$old" ] && kill -0 "$old" 2>/dev/null; then
log "SKIP: previous run still alive"; exit 0
fi
rm -f "$LOCK/pid"; rmdir "$LOCK" 2>/dev/null
mkdir "$LOCK" 2>/dev/null || { log "SKIP: lost lock race"; exit 0; }
fi
echo $$ > "$LOCK/pid"
trap 'rm -f "$LOCK/pid"; rmdir "$LOCK" 2>/dev/null' EXIT
cd "${NIGHTLY_WORKDIR:-$PWD}" || { log "FAIL: cannot cd"; exit 2; }
rm -f "$STATE_DIR/timed_out"
claude --bare -p "${NIGHTLY_PROMPT:-List TODO comments added in the last 7 days of git log.}" \
--output-format json --permission-mode dontAsk \
--allowedTools "Read" --max-budget-usd 1 \
> "$OUT" 2> "$STATE_DIR/stderr.log" &
cpid=$!
# 純 bash 看門狗;逾時留記號,不靠退出碼判斷
( sleep "$TIMEOUT_SEC"; touch "$STATE_DIR/timed_out"; kill -TERM "$cpid"; sleep 5; kill -KILL "$cpid" ) 2>/dev/null &
wpid=$!; disown "$wpid"
wait "$cpid"; rc=$?
kill "$wpid" 2>/dev/null
[ -e "$STATE_DIR/timed_out" ] && { log "FAIL: timeout"; exit 124; }
[ "$rc" -ne 0 ] && { log "FAIL: claude exited $rc"; exit "$rc"; }
# 退出碼 0 不代表成功:is_error、subtype、permission_denials 都要看
jq -e . "$OUT" >/dev/null 2>&1 || { log "FAIL: not valid JSON"; exit 65; }
[ "$(jq -r '.is_error // false' "$OUT")" = "true" ] && { log "FAIL: is_error"; exit 1; }
[ "$(jq -r '.subtype // ""' "$OUT")" = "success" ] || { log "FAIL: subtype"; exit 1; }
[ "$(jq '.permission_denials // [] | length' "$OUT")" -eq 0 ] || { log "FAIL: permission denied"; exit 3; }
log "OK: cost_usd=$(jq -r '.total_cost_usd // "?"' "$OUT")"
幾個設計理由:
claude 找不到、密鑰讀不到是最常見的第一次失敗。env 檔要用 set -a 載入,變數才會傳給 claude。--bare 是官方建議的腳本模式,不加的話 -p 會載入專案的 hooks、.mcp.json 與你的 allow 規則,範圍就不只旗標列的那些。代價是 bare 不讀 OAuth 與 keychain,要設 ANTHROPIC_API_KEY,不能用訂閱登入。flock,用 mkdir 的原子性代替。--max-budget-usd 限花費不限時間,兩個都要設。--permission-mode dontAsk 把所有會問的呼叫直接拒絕,--allowedTools 只是額外放行,要拿掉工具得用 --tools。dontAsk 下唯讀的 shell 指令(grep、find、git log)本來就不用問,所以示範提示選了唯讀做得到的事。起始 mode 在某些情況下可能是 auto,要自己明確傳。官方還有 --permission-prompts none:沒有人能批准時,讓 Claude 知道不要重試,有 permission host(SDK 或 --permission-prompt-tool)時差別最大。is_error、subtype 與 permission_denials 三個欄位。排程裡後果更大,因為沒人看。--settings 打開 sandbox。如果用 launchd,放一份 plist 指到這支腳本,以 StartCalendarInterval 設時間,並把 StandardOutPath 與 StandardErrorPath 指到狀態目錄。兩者對睡眠的處理不同:cron 睡過就跳過,launchd 在電腦醒來時補跑,多次錯過會合併成一次。所以提示一樣要會看時間。
一、權限要在任務跑之前決定好。 排程任務碰到的權限提示,沒有人會答。Desktop 的 Manual mode 會卡住,雲端 Routines 乾脆沒有提示全部放行,claude -p 看你傳的旗標。所以重點不在選哪個 mode,而在事先把任務能用的工具範圍縮到剛好。Day 26 的兩層在這裡用得上:權限規則比對指令文字,決定放不放行;sandbox 與外層隔離圍住行程實際能碰到的東西。無人看管的任務兩層都要。
二、綠燈不等於做成。 三個層次要分開看:

Routines 的 run 列表,綠色狀態只代表 session 啟動並且沒有基礎設施錯誤地結束,不代表提示裡的任務成功。被擋的網路請求、缺少的 connector 工具、任務層級的失敗,都只會出現在 transcript 裡。要查原因可以問 Claude:/schedule why did my nightly review do nothing this morning?,它會列出最近幾次 run,讀 log 解釋工具錯誤與權限拒絕。
三、提示要自足,還要寫成功長什麼樣。 排程的提示沒人可以追問,必須自成一體,明確說要做什麼、成功長什麼樣。再補兩個:已經過了該跑的時間要怎麼辦(補跑情境),以及沒事的時候怎麼回報(例如「一切正常就用一行說明」,官方的 loop.md 範例這樣收尾)。


| 機制 | 漏跑時 |
|---|---|
/loop |
不補跑,閒下來後只觸發一次;session 退出就停 |
| Desktop 排程 | 該次標 skipped,醒來補最近一次,只回溯七天 |
| 雲端 Routines | 超過每小時上限的排程會延後;GitHub 事件超量會被丟棄;GitHub 連線失效時跳過,72 小時內重連就恢復,超過就關閉 |
| GitHub Actions | 高負載時延遲或丟棄,不補跑,整點最擠;公開 repo 閒置六十天停用 |
| 系統排程 | cron 睡過就跳過;launchd 醒來補一次,多次合併成一次 |
漏一次不能接受的任務,有兩個辦法:讓任務冪等且可補(每次從上次完成的位置起算,而不是固定的時間窗),或加一個外部檢查,盯著最後一次成功的時間,太久沒更新就告警。後者放在任務之外,因為任務不知道自己沒跑。
| 情況 | 可以選 |
|---|---|
| 這個 session 內等部署、盯 CI、看長 build | /loop 或 Monitor |
| 等事件發生,或做到條件成立為止 | Monitor、Channels 或 /goal,不用輪詢 |
| 每天要跑、要碰本機檔案或本機 MCP,電腦通常開著 | Desktop 排程,建好後先 Run now 把權限選完 |
| 每天要跑、電腦可能關著、不需要本機檔案 | 雲端 Routines |
| 已經在 GitHub 上、工作跟 repo 事件綁在一起 | GitHub Actions |
| 要完全控制環境、重試、通知,或者已有排程基礎設施 | 系統排程加 claude -p,自己寫鎖與逾時 |
/loop 屬於 session,七天過期、不補跑。Desktop 排程要電腦醒著。雲端 Routines 不依賴本機,但每次是全新 clone,沒有權限提示。claude -p,環境、鎖、逾時、輸出檢查都要自己寫。